//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include "client_pch.h"
#include "networkstringtabledefs.h"
#include <checksum_md5.h>
#include <iregistry.h>
#include "pure_server.h"
#include "netmessages.h"
#include "cl_demo.h"
#include "host_state.h"
#include "host.h"
#include "gl_matsysiface.h"
#include "vgui_baseui_interface.h"
#include "tier0/icommandline.h"
#include <proto_oob.h>
#include "checksum_engine.h"
#include "filesystem_engine.h"
#include "logofile_shared.h"
#include "sound.h"
#include "decal.h"
#include "networkstringtableclient.h"
#include "dt_send_eng.h"
#include "ents_shared.h"
#include "cl_ents_parse.h"
#include "cl_entityreport.h"
#include "MapReslistGenerator.h"
#include "DownloadListGenerator.h"
#include "GameEventManager.h"
#include "host_phonehome.h"
#include "vgui_baseui_interface.h"
#include "clockdriftmgr.h"
#include "snd_audio_source.h"
#include "vgui_controls/Controls.h"
#include "vgui/ILocalize.h"
#include "download.h"
#include "checksum_engine.h"
#include "ModelInfo.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/materialsystem_config.h"
#include "tier1/fmtstr.h"
#include "cl_steamauth.h"
#include "matchmaking.h"
#include "server.h"
#include "eiface.h"

#include "tier0/platform.h"
#include "tier0/systeminformation.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

static ConVar cl_timeout( "cl_timeout", "30", FCVAR_ARCHIVE, "After this many seconds without receiving a packet from the server, the client will disconnect itself" );
	   ConVar cl_logofile( "cl_logofile", "materials/decals/spraylogo.vtf", FCVAR_ARCHIVE, "Spraypoint logo decal." ); // TODO must be more generic
static ConVar cl_soundfile( "cl_soundfile", "sound/player/jingle.wav", FCVAR_ARCHIVE, "Jingle sound file." );
static ConVar cl_allowdownload ( "cl_allowdownload", "1", FCVAR_ARCHIVE, "Client downloads customization files" );
static ConVar cl_downloadfilter( "cl_downloadfilter", "all", FCVAR_ARCHIVE, "Determines which files can be downloaded from the server (all, none, nosounds, mapsonly)" );

#ifdef OSX
	// OS X is barely making it due to virtual memory pressure on 32bit, our behavior of load new models -> unload
	// unused is far too abusive for its estimated margin of maybe two or three bytes before crashing.
	#define CONVAR_DEFAULT_ALWAYS_FLUSH_MODELS "1"
#else
	#define CONVAR_DEFAULT_ALWAYS_FLUSH_MODELS "0"
#endif
static ConVar cl_always_flush_models( "cl_always_flush_models", CONVAR_DEFAULT_ALWAYS_FLUSH_MODELS, FCVAR_INTERNAL_USE,
                                      "If set, always flush models between map loads.  Useful on systems under memory pressure." );

extern ConVar sv_downloadurl;

#if defined( _DEBUG ) || defined( STAGING_ONLY )
static ConVar debug_clientstate_fake_hltv( "debug_clientstate_fake_hltv", "0", 0, "If set, spoof as HLTV for testing purposes." );
#if defined( REPLAY_ENABLED )
static ConVar debug_clientstate_fake_replay( "debug_clientstate_fake_replay", "0", 0, "If set, spoof as REPLAY for testing purposes." );
#endif // defined( REPLAY_ENABLED )
#endif // defined( _DEBUG ) || defined( STAGING_ONLY )

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CClientState::CClientState()
{
	m_bMarkedCRCsUnverified = false;
	demonum = -1;
	m_tickRemainder = 0;
	m_frameTime = 0;
	m_pAreaBits = NULL;
	m_hWaitForResourcesHandle = NULL;
	m_bUpdateSteamResources = false;
	m_bPrepareClientDLL = false;
	m_bShownSteamResourceUpdateProgress = false;
	m_pPureServerWhitelist = NULL;
	m_pPendingPureFileReloads = NULL;
	m_bCheckCRCsWithServer = false;
	m_flLastCRCBatchTime = 0;
	m_nFriendsID = 0;
	m_FriendsName[0] = 0;
#if defined( REPLAY_ENABLED )
	isreplay = false;
#endif
}

CClientState::~CClientState()
{
	if ( m_pPureServerWhitelist )
		m_pPureServerWhitelist->Release();
}

// HL1 CD Key
#define GUID_LEN 13

/*
=======================
CL_GetCDKeyHash()

Connections will now use a hashed cd key value
A LAN server will know not to allows more then xxx users with the same CD Key
=======================
*/
const char *CClientState::GetCDKeyHash( void )
{
	if ( IsPC() )
	{
		char szKeyBuffer[256]; // Keys are about 13 chars long.	
		static char szHashedKeyBuffer[64];
		int nKeyLength;
		bool bDedicated = false;

		MD5Context_t ctx;
		unsigned char digest[16]; // The MD5 Hash

		nKeyLength = Q_snprintf( szKeyBuffer, sizeof( szKeyBuffer ), "%s", registry->ReadString( "key", "" ) );

		if (bDedicated)
		{
			ConMsg("Key has no meaning on dedicated server...\n");
			return "";
		}

		if ( nKeyLength == 0 )
		{
			nKeyLength = 13;
			Q_strncpy( szKeyBuffer, "1234567890123", sizeof( szKeyBuffer ) );
			Assert( Q_strlen( szKeyBuffer ) == nKeyLength );

			DevMsg( "Missing CD Key from registry, inserting blank key\n" );

			registry->WriteString( "key", szKeyBuffer );
		}

		if (nKeyLength <= 0 ||
			nKeyLength >= 256 )
		{
			ConMsg("Bogus key length on CD Key...\n");
			return "";
		}

		// Now get the md5 hash of the key
		memset( &ctx, 0, sizeof( ctx ) );
		memset( digest, 0, sizeof( digest ) );
		
		MD5Init(&ctx);
		MD5Update(&ctx, (unsigned char*)szKeyBuffer, nKeyLength);
		MD5Final(digest, &ctx);
		Q_strncpy ( szHashedKeyBuffer, MD5_Print ( digest, sizeof( digest ) ), sizeof( szHashedKeyBuffer ) );
		return szHashedKeyBuffer;
	}

	return "12345678901234567890123456789012";
}

void CClientState::SendClientInfo( void )
{
	CLC_ClientInfo info;
	
	info.m_nSendTableCRC = SendTable_GetCRC();
	info.m_nServerCount = m_nServerCount;
	info.m_bIsHLTV = false;
#if defined( REPLAY_ENABLED )
	info.m_bIsReplay = false;
#endif
#if !defined( NO_STEAM )
	info.m_nFriendsID = Steam3Client().SteamUser() ? Steam3Client().SteamUser()->GetSteamID().GetAccountID() : 0;
#else
	info.m_nFriendsID = 0;
#endif
	Q_strncpy( info.m_FriendsName, m_FriendsName, sizeof(info.m_FriendsName) );

	CheckOwnCustomFiles(); // load & verfiy custom player files

	for ( int i=0; i< MAX_CUSTOM_FILES; i++ )
		info.m_nCustomFiles[i] = m_nCustomFiles[i].crc;

	// Testing to ensure we don't blow up servers by faking our client info
#if defined( _DEBUG ) || defined( STAGING_ONLY )
	if ( debug_clientstate_fake_hltv.GetBool() )
	{
		Msg( "!! Spoofing connect as HLTV in SendClientInfo\n" );
		info.m_bIsHLTV = true;
	}
#if defined( REPLAY_ENABLED )
	if ( debug_clientstate_fake_replay.GetBool() )
	{
		Msg( "!! Spoofing connect as REPLAY in SendClientInfo\n" );
		info.m_bIsReplay = true;
	}
#endif // defined( REPLAY_ENABLED )
#endif // defined( _DEBUG ) || defined( STAGING_ONLY )
	m_NetChannel->SendNetMsg( info );
}

void CClientState::SendServerCmdKeyValues( KeyValues *pKeyValues )
{
	if ( !pKeyValues )
		return;
	
	CLC_CmdKeyValues clcCommand( pKeyValues );

	if ( !m_NetChannel )
		return;

	m_NetChannel->SendNetMsg( clcCommand );
}

extern IVEngineClient *engineClient;

//-----------------------------------------------------------------------------
// Purpose: A svc_signonnum has been received, perform a client side setup
// Output : void CL_SignonReply
//-----------------------------------------------------------------------------
bool CClientState::SetSignonState ( int state, int count )
{
	if ( !CBaseClientState::SetSignonState( state, count ) )
	{
		CL_Retry();
		return false;
	}

	// ConDMsg ("Signon state: %i\n", state );

	COM_TimestampedLog( "CClientState::SetSignonState: start %i", state );

	switch ( m_nSignonState )
	{
		case SIGNONSTATE_CHALLENGE	:	
			m_bMarkedCRCsUnverified = false;	// Remember that we just connected to a new server so it'll 
												// reverify any necessary file CRCs on this server.
			EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONCHALLENGE);
			break;

		case SIGNONSTATE_CONNECTED :	
			{
				EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONCONNECTED);
				
				// make sure it's turned off when connecting
				EngineVGui()->HideDebugSystem();

				SCR_BeginLoadingPlaque ();
				// Clear channel and stuff
				m_NetChannel->Clear();

				// allow longer timeout
				m_NetChannel->SetTimeout( SIGNON_TIME_OUT );
				m_NetChannel->SetMaxBufferSize( true, NET_MAX_PAYLOAD );
				
				// set user settings (rate etc)
				NET_SetConVar convars;
				Host_BuildConVarUpdateMessage( &convars, FCVAR_USERINFO, false );
				m_NetChannel->SendNetMsg( convars );
			}
			break;

		case SIGNONSTATE_NEW :	
			{
				EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONNEW);

				if ( IsPC() && !demoplayer->IsPlayingBack() )
				{
					// start making sure we have all the specified resources
					StartUpdatingSteamResources();
				}
				else
				{
					// during demo playback dont try to download resource
					FinishSignonState_New();
				}

				// don't tell the server yet that we've entered this state
				return true;
			}
			break;

		case SIGNONSTATE_PRESPAWN	:
			m_nSoundSequence = 1;	// reset sound sequence number after receiving signon sounds
			break;
		
		case SIGNONSTATE_SPAWN :
			{
				Assert( g_ClientDLL );

				EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONSPAWN);

				// Tell client .dll about the transition
				char mapname[256];
				CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), mapname, sizeof( mapname ) );

				COM_TimestampedLog( "LevelInitPreEntity: start %d", state );
				g_ClientDLL->LevelInitPreEntity(mapname);
				COM_TimestampedLog( "LevelInitPreEntity: end %d", state );

				phonehome->Message( IPhoneHome::PHONE_MSG_MAPSTART, mapname );

				audiosourcecache->LevelInit( mapname );

				// stop recording demo header
				demorecorder->SetSignonState( SIGNONSTATE_SPAWN );
			}
			break;

		case SIGNONSTATE_FULL:
			{
				CL_FullyConnected();
				if ( m_NetChannel )
				{
					m_NetChannel->SetTimeout( cl_timeout.GetFloat() );
					m_NetChannel->SetMaxBufferSize( true, NET_MAX_DATAGRAM_PAYLOAD );
				}

				HostState_OnClientConnected();
				
				if ( m_nMaxClients > 1 )
				{
					g_pMatchmaking->AddLocalPlayersToTeams();
				}
			}
			break;

		case SIGNONSTATE_CHANGELEVEL:	
			m_NetChannel->SetTimeout( SIGNON_TIME_OUT );  // allow 5 minutes timeout
			if ( m_nMaxClients > 1 )
			{
				// start progress bar immediately for multiplayer level transitions
				EngineVGui()->EnabledProgressBarForNextLoad();
			}
			SCR_BeginLoadingPlaque();
			if ( m_nMaxClients > 1 )
			{
				EngineVGui()->UpdateProgressBar(PROGRESS_CHANGELEVEL);
			}
			break;
	}

	COM_TimestampedLog( "CClientState::SetSignonState: end %i", state );

	if ( state >= SIGNONSTATE_CONNECTED && m_NetChannel )
	{
		// tell server that we entered now that state
		m_NetChannel->SendNetMsg( NET_SignonState( state, count) );
	}

	return true;
}

bool CClientState::HookClientStringTable( char const *tableName )
{
	INetworkStringTable *table = GetStringTable( tableName );
	if ( !table )
	{
		// If engine takes a pass, allow client dll to hook in its callbacks
		if ( g_ClientDLL )
		{
			g_ClientDLL->InstallStringTableCallback( tableName );
		}
        return false;
	}

#if 0
	// This was added into staging at some point and is not enabled in main or rel.
	char szDownloadableFileTablename[255] = DOWNLOADABLE_FILE_TABLENAME;
	char szModelPrecacheTablename[255] = MODEL_PRECACHE_TABLENAME;
	char szGenericPrecacheTablename[255] = GENERIC_PRECACHE_TABLENAME;
	char szSoundPrecacheTablename[255] = SOUND_PRECACHE_TABLENAME;
	char szDecalPrecacheTablename[255] = DECAL_PRECACHE_TABLENAME;

	Q_snprintf( szDownloadableFileTablename, 255, ":%s", DOWNLOADABLE_FILE_TABLENAME );
	Q_snprintf( szModelPrecacheTablename, 255, ":%s", MODEL_PRECACHE_TABLENAME );
	Q_snprintf( szGenericPrecacheTablename, 255, ":%s", GENERIC_PRECACHE_TABLENAME );
	Q_snprintf( szSoundPrecacheTablename, 255, ":%s", SOUND_PRECACHE_TABLENAME );
	Q_snprintf( szDecalPrecacheTablename, 255, ":%s", DECAL_PRECACHE_TABLENAME );		
#else
	const char *szDownloadableFileTablename = DOWNLOADABLE_FILE_TABLENAME;
	const char *szModelPrecacheTablename = MODEL_PRECACHE_TABLENAME;
	const char *szGenericPrecacheTablename = GENERIC_PRECACHE_TABLENAME;
	const char *szSoundPrecacheTablename = SOUND_PRECACHE_TABLENAME;
	const char *szDecalPrecacheTablename = DECAL_PRECACHE_TABLENAME;
#endif

	// Hook Model Precache table
	if ( !Q_strcasecmp( tableName, szModelPrecacheTablename ) )
	{
		m_pModelPrecacheTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, szGenericPrecacheTablename ) )
	{
		m_pGenericPrecacheTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, szSoundPrecacheTablename ) )
	{
		m_pSoundPrecacheTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, szDecalPrecacheTablename ) )
	{
		// Cache the id
		m_pDecalPrecacheTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, INSTANCE_BASELINE_TABLENAME ) )
	{
		// Cache the id
		m_pInstanceBaselineTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, LIGHT_STYLES_TABLENAME ) )
	{
		// Cache the id
		m_pLightStyleTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, USER_INFO_TABLENAME ) )
	{
		// Cache the id
		m_pUserInfoTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, SERVER_STARTUP_DATA_TABLENAME ) )
	{
		// Cache the id
		m_pServerStartupTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, szDownloadableFileTablename ) )
	{
		// Cache the id
		m_pDownloadableFileTable = table;
		return true;
	}

	if ( !Q_strcasecmp( tableName, "DynamicModels" ) )
	{
		m_pDynamicModelsTable = table;
		return true;
	}

	// If engine takes a pass, allow client dll to hook in its callbacks
	g_ClientDLL->InstallStringTableCallback( tableName );

	return false;
}

bool CClientState::InstallEngineStringTableCallback( char const *tableName )
{
	INetworkStringTable *table = GetStringTable( tableName );

	if ( !table )
		return false;

#if 0
	// This was added into staging at some point and is not enabled in main or rel.
	char szDownloadableFileTablename[255] = DOWNLOADABLE_FILE_TABLENAME;
	char szModelPrecacheTablename[255] = MODEL_PRECACHE_TABLENAME;
	char szGenericPrecacheTablename[255] = GENERIC_PRECACHE_TABLENAME;
	char szSoundPrecacheTablename[255] = SOUND_PRECACHE_TABLENAME;
	char szDecalPrecacheTablename[255] = DECAL_PRECACHE_TABLENAME;

	Q_snprintf( szDownloadableFileTablename, 255, ":%s", DOWNLOADABLE_FILE_TABLENAME );
	Q_snprintf( szModelPrecacheTablename, 255, ":%s", MODEL_PRECACHE_TABLENAME );
	Q_snprintf( szGenericPrecacheTablename, 255, ":%s", GENERIC_PRECACHE_TABLENAME );
	Q_snprintf( szSoundPrecacheTablename, 255, ":%s", SOUND_PRECACHE_TABLENAME );
	Q_snprintf( szDecalPrecacheTablename, 255, ":%s", DECAL_PRECACHE_TABLENAME );		
#else
	const char *szDownloadableFileTablename = DOWNLOADABLE_FILE_TABLENAME;
	const char *szModelPrecacheTablename = MODEL_PRECACHE_TABLENAME;
	const char *szGenericPrecacheTablename = GENERIC_PRECACHE_TABLENAME;
	const char *szSoundPrecacheTablename = SOUND_PRECACHE_TABLENAME;
	const char *szDecalPrecacheTablename = DECAL_PRECACHE_TABLENAME;
#endif

	// Hook Model Precache table
	if ( !Q_strcasecmp( tableName, szModelPrecacheTablename ) )
	{
		table->SetStringChangedCallback( NULL, Callback_ModelChanged );
		return true;
	}

	if ( !Q_strcasecmp( tableName, szGenericPrecacheTablename ) )
	{
		// Install the callback
		table->SetStringChangedCallback( NULL, Callback_GenericChanged );
		return true;
	}

	if ( !Q_strcasecmp( tableName, szSoundPrecacheTablename ) )
	{
		// Install the callback
		table->SetStringChangedCallback( NULL, Callback_SoundChanged );
		return true;
	}

	if ( !Q_strcasecmp( tableName, szDecalPrecacheTablename ) )
	{
		// Install the callback
		table->SetStringChangedCallback( NULL, Callback_DecalChanged );
		return true;
	}

	if ( !Q_strcasecmp( tableName, INSTANCE_BASELINE_TABLENAME ) )
	{
		// Install the callback (already done above)
		table->SetStringChangedCallback( NULL, Callback_InstanceBaselineChanged );
		return true;
	}

	if ( !Q_strcasecmp( tableName, LIGHT_STYLES_TABLENAME ) )
	{
		return true;
	}

	if ( !Q_strcasecmp( tableName, USER_INFO_TABLENAME ) )
	{
		// Install the callback
		table->SetStringChangedCallback( NULL, Callback_UserInfoChanged );
		return true;
	}

	if ( !Q_strcasecmp( tableName, SERVER_STARTUP_DATA_TABLENAME ) )
	{
		return true;
	}

	if ( !Q_strcasecmp( tableName, szDownloadableFileTablename ) )
	{
		return true;
	}

	if ( !Q_strcasecmp( tableName, "DynamicModels" ) )
	{
		table->SetStringChangedCallback( NULL, Callback_DynamicModelsChanged );
		m_pDynamicModelsTable = table;
		return true;
	}

	// The the client.dll have a shot at it
	return false;
}

void CClientState::InstallStringTableCallback( char const *tableName )
{
	// Let engine hook callbacks before we read in any data values at all
	if ( !InstallEngineStringTableCallback( tableName ) )
	{
		// If engine takes a pass, allow client dll to hook in its callbacks
		g_ClientDLL->InstallStringTableCallback( tableName );
	}
}

bool CClientState::IsPaused() const
{
	return m_bPaused || ( g_LostVideoMemory && Host_IsSinglePlayerGame() ) ||
		!host_initialized || 
		demoplayer->IsPlaybackPaused() ||
		EngineVGui()->ShouldPause();
}

float CClientState::GetTime() const
{
	int nTickCount = GetClientTickCount();
	float flTickTime = nTickCount * host_state.interval_per_tick;
	
	// Timestamps are rounded to exact tick during simulation
	if ( insimulation )
	{
		return flTickTime;
	}

	return flTickTime + m_tickRemainder;
}

float CClientState::GetFrameTime() const
{
	if ( CClockDriftMgr::IsClockCorrectionEnabled() )
	{
		return IsPaused() ? 0 : m_frameTime;
	}
	else
	{
		if ( insimulation )
		{
			int nElapsedTicks = ( GetClientTickCount() - oldtickcount );
			return nElapsedTicks * host_state.interval_per_tick;
		}
		else
		{
			return IsPaused() ? 0 : m_frameTime;
		}
	}
}

float CClientState::GetClientInterpAmount()
{
	// we need client cvar cl_interp_ratio
	static const ConVar *s_cl_interp_ratio = NULL;
	if ( !s_cl_interp_ratio )
	{
		s_cl_interp_ratio = g_pCVar->FindVar( "cl_interp_ratio" );
		if ( !s_cl_interp_ratio )
			return 0.1f;
	}
	static const ConVar *s_cl_interp = NULL;
	if ( !s_cl_interp )
	{
		s_cl_interp = g_pCVar->FindVar( "cl_interp" );
		if ( !s_cl_interp )
			return 0.1f;
	}
		
	float flInterpRatio = s_cl_interp_ratio->GetFloat();
	float flInterp = s_cl_interp->GetFloat();

	const ConVar_ServerBounded *pBounded = static_cast<const ConVar_ServerBounded*>( s_cl_interp_ratio );
	if ( pBounded )
		flInterpRatio = pBounded->GetFloat();
	//#define FIXME_INTERP_RATIO
	return max( flInterpRatio / cl_updaterate->GetFloat(), flInterp );
}

//-----------------------------------------------------------------------------
// Purpose: // Clear all the variables in the CClientState.
//-----------------------------------------------------------------------------
void CClientState::Clear( void )
{
	CBaseClientState::Clear();

	m_pModelPrecacheTable = NULL;
	m_pGenericPrecacheTable = NULL;
	m_pSoundPrecacheTable = NULL;
	m_pDecalPrecacheTable = NULL;
	m_pInstanceBaselineTable = NULL;
	m_pLightStyleTable = NULL;
	m_pUserInfoTable = NULL;
	m_pServerStartupTable = NULL;
	m_pDynamicModelsTable = NULL;
	m_pAreaBits = NULL;

	// Clear all download vars.
	m_pDownloadableFileTable = NULL;
	m_hWaitForResourcesHandle = NULL;
	m_bUpdateSteamResources = false;
	m_bShownSteamResourceUpdateProgress = false;
	m_bDownloadResources = false;
	m_bPrepareClientDLL = false;

	DeleteClientFrames( -1 ); // clear all
		
	viewangles.Init();
	m_flLastServerTickTime = 0.0f;
	oldtickcount = 0;
	insimulation = false;


	addangle.RemoveAll();
	addangletotal = 0.0f;
	prevaddangletotal = 0.0f;

	memset(model_precache, 0, sizeof(model_precache));
	memset(sound_precache, 0, sizeof(sound_precache));
	ishltv = false;
#if defined( REPLAY_ENABLED )
	isreplay = false;
#endif
	cdtrack = 0;
	V_memset( serverMD5.bits, 0, MD5_DIGEST_LENGTH );
	last_command_ack = 0;
	command_ack = 0;
	m_nSoundSequence = 0;

	// make sure the client isn't active anymore, but stay
	// connected if we are.
	if ( m_nSignonState > SIGNONSTATE_CONNECTED )
	{
		m_nSignonState = SIGNONSTATE_CONNECTED;
	}
}

void CClientState::ClearSounds()
{
	int c = ARRAYSIZE( sound_precache );
	for ( int i = 0; i < c; ++i )
	{
		sound_precache[ i ].SetSound( NULL );
	}
}

bool CClientState::ProcessConnectionlessPacket( netpacket_t *packet )
{
	Assert( packet );

	return CBaseClientState::ProcessConnectionlessPacket( packet );
}

void CClientState::FullConnect( netadr_t &adr )
{
	CBaseClientState::FullConnect( adr );
	m_NetChannel->SetDemoRecorder( g_pClientDemoRecorder );
	m_NetChannel->SetDataRate( cl_rate->GetFloat() );

	// Not in the demo loop now
	demonum = -1;		
	
	// We don't have a backed up cmd history yet
	lastoutgoingcommand = -1;

	// we didn't send commands yet
	chokedcommands = 0;
	
	// Report connection success.
	if ( Q_stricmp("loopback", adr.ToString() ) )
	{
		ConMsg( "Connected to %s\n", adr.ToString() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
// Output : model_t
//-----------------------------------------------------------------------------
model_t *CClientState::GetModel( int index )
{
	if ( !m_pModelPrecacheTable )
	{
		return NULL;
	}

	if ( index <= 0 )
	{
		return NULL;
	}

	if ( index >= m_pModelPrecacheTable->GetNumStrings() )
	{
		Assert( 0 ); // model index for unkown model requested
		return NULL;
	}

	CPrecacheItem *p = &model_precache[ index ];
	model_t *m = p->GetModel();
	if ( m )
	{
		return m;
	}

	// The world model has special handling and should not be lazy loaded here
	if ( index == 1 )
	{
		Assert( false );
		Warning( "Attempting to get world model before it was loaded\n" );
		return NULL;
	}

	char const *name = m_pModelPrecacheTable->GetString( index );

	if ( host_showcachemiss.GetBool() )
	{
		ConDMsg( "client model cache miss on %s\n", name );
	}

	m = modelloader->GetModelForName( name, IModelLoader::FMODELLOADER_CLIENT );
	if ( !m )
	{
		const CPrecacheUserData *data = CL_GetPrecacheUserData( m_pModelPrecacheTable, index );
		if ( data && ( data->flags & RES_FATALIFMISSING ) )
		{
			COM_ExplainDisconnection( true, "Cannot continue without model %s, disconnecting\n", name );
			Host_Disconnect( true, "Missing model" );
		}
	}

	p->SetModel( m );
	return m;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int -- note -1 if missing
//-----------------------------------------------------------------------------
int CClientState::LookupModelIndex( char const *name )
{
	if ( !m_pModelPrecacheTable )
	{
		return -1;
	}
	int idx = m_pModelPrecacheTable->FindStringIndex( name );
	return ( idx == INVALID_STRING_INDEX ) ? -1 : idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
//			*name - 
//-----------------------------------------------------------------------------
void CClientState::SetModel( int tableIndex )
{
	if ( !m_pModelPrecacheTable )
	{
		return;
	}

	// Bogus index
	if ( tableIndex < 0 || tableIndex >= m_pModelPrecacheTable->GetNumStrings() )
	{
		return;
	}

	char const *name = m_pModelPrecacheTable->GetString( tableIndex );

	if ( tableIndex == 1 )
	{
		// The world model must match the LevelFileName -- it is the path we just checked the CRC for, and paths may differ
		// from what the server is using based on what the gameDLL override does
		name = m_szLevelFileName;
	}

	CPrecacheItem *p = &model_precache[ tableIndex ];
	const CPrecacheUserData *data = CL_GetPrecacheUserData( m_pModelPrecacheTable, tableIndex );

	bool bLoadNow = ( data && ( data->flags & RES_PRELOAD ) ) || IsX360();
	if ( CommandLine()->FindParm( "-nopreload" ) ||	CommandLine()->FindParm( "-nopreloadmodels" ))
	{
		bLoadNow = false;
	}
	else if ( CommandLine()->FindParm( "-preload" ) )
	{
		bLoadNow = true;
	}

	if ( bLoadNow )
	{
		p->SetModel( modelloader->GetModelForName( name, IModelLoader::FMODELLOADER_CLIENT ) );
	}
	else
	{
		p->SetModel( NULL );
	}

	// log the file reference, if necssary
	if (MapReslistGenerator().IsEnabled())
	{
		name = m_pModelPrecacheTable->GetString( tableIndex );
		MapReslistGenerator().OnModelPrecached( name );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
// Output : model_t
//-----------------------------------------------------------------------------
char const *CClientState::GetGeneric( int index )
{
	if ( !m_pGenericPrecacheTable )
	{
		Warning( "Can't GetGeneric( %d ), no precache table [no level loaded?]\n", index );
		return "";
	}

	if ( index <= 0 )
		return "";

	if ( index >= m_pGenericPrecacheTable->GetNumStrings() )
	{
		return "";
	}

	CPrecacheItem *p = &generic_precache[ index ];
	char const *g = p->GetGeneric();
	return g;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int -- note -1 if missing
//-----------------------------------------------------------------------------
int CClientState::LookupGenericIndex( char const *name )
{
	if ( !m_pGenericPrecacheTable )
	{
		Warning( "Can't LookupGenericIndex( %s ), no precache table [no level loaded?]\n", name );
		return -1;
	}
	int idx = m_pGenericPrecacheTable->FindStringIndex( name );
	return ( idx == INVALID_STRING_INDEX ) ? -1 : idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
//			*name - 
//-----------------------------------------------------------------------------
void CClientState::SetGeneric( int tableIndex )
{
	if ( !m_pGenericPrecacheTable )
	{
		Warning( "Can't SetGeneric( %d ), no precache table [no level loaded?]\n", tableIndex );
		return;
	}
	// Bogus index
	if ( tableIndex < 0 || 
		 tableIndex >= m_pGenericPrecacheTable->GetNumStrings() )
	{
		return;
	}

	char const *name = m_pGenericPrecacheTable->GetString( tableIndex );
	CPrecacheItem *p = &generic_precache[ tableIndex ];
	p->SetGeneric( name );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
// Output : char const
//-----------------------------------------------------------------------------
char const *CClientState::GetSoundName( int index )
{
	if ( index <= 0 || !m_pSoundPrecacheTable )
		return "";

	if ( index >= m_pSoundPrecacheTable->GetNumStrings() )
	{
		return "";
	}

	char const *name = m_pSoundPrecacheTable->GetString( index );
	return name;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
// Output : model_t
//-----------------------------------------------------------------------------
CSfxTable *CClientState::GetSound( int index )
{
	if ( index <= 0 || !m_pSoundPrecacheTable )
		return NULL;

	if ( index >= m_pSoundPrecacheTable->GetNumStrings() )
	{
		return NULL;
	}

	CPrecacheItem *p = &sound_precache[ index ];
	CSfxTable *s = p->GetSound();
	if ( s )
		return s;

	char const *name = m_pSoundPrecacheTable->GetString( index );

	if ( host_showcachemiss.GetBool() )
	{
		ConDMsg( "client sound cache miss on %s\n", name );
	}

	s = S_PrecacheSound( name );

	p->SetSound( s );
	return s;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int -- note -1 if missing
//-----------------------------------------------------------------------------
int CClientState::LookupSoundIndex( char const *name )
{
	if ( !m_pSoundPrecacheTable )
		return -1;

	int idx = m_pSoundPrecacheTable->FindStringIndex( name );
	return ( idx == INVALID_STRING_INDEX ) ? -1 : idx;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
//			*name - 
//-----------------------------------------------------------------------------
void CClientState::SetSound( int tableIndex )
{
	// Bogus index
	if ( !m_pSoundPrecacheTable )
		return;

	if ( tableIndex < 0 || tableIndex >= m_pSoundPrecacheTable->GetNumStrings() )
	{
		return;
	}

	CPrecacheItem *p = &sound_precache[ tableIndex ];
	const CPrecacheUserData *data = CL_GetPrecacheUserData( m_pSoundPrecacheTable, tableIndex );

	bool bLoadNow = ( data && ( data->flags & RES_PRELOAD ) ) || IsX360();
	if ( CommandLine()->FindParm( "-nopreload" ) ||	CommandLine()->FindParm( "-nopreloadsounds" ))
	{
		bLoadNow = false;
	}
	else if ( CommandLine()->FindParm( "-preload" ) )
	{
		bLoadNow = true;
	}

	if ( bLoadNow )
	{
		char const *name = m_pSoundPrecacheTable->GetString( tableIndex );
		p->SetSound( S_PrecacheSound( name ) );
	}
	else
	{
		p->SetSound( NULL );
	}

	// log the file reference, if necssary
	if (MapReslistGenerator().IsEnabled())
	{
		char const *name = m_pSoundPrecacheTable->GetString( tableIndex );
		MapReslistGenerator().OnSoundPrecached( name );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
// Output : model_t
//-----------------------------------------------------------------------------
char const *CClientState::GetDecalName( int index )
{
	if ( index <= 0 || !m_pDecalPrecacheTable )
	{
		return NULL;
	}

	if ( index >= m_pDecalPrecacheTable->GetNumStrings() )
	{
		return NULL;
	}

	CPrecacheItem *p = &decal_precache[ index ];
	char const *d = p->GetDecal();
	return d;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
//			*name - 
//-----------------------------------------------------------------------------
void CClientState::SetDecal( int tableIndex )
{
	if ( !m_pDecalPrecacheTable )
		return;

	if ( tableIndex < 0 || 
		 tableIndex >= m_pDecalPrecacheTable->GetNumStrings() )
	{
		return;
	}

	char const *name = m_pDecalPrecacheTable->GetString( tableIndex );
	CPrecacheItem *p = &decal_precache[ tableIndex ];
	p->SetDecal( name );

	Draw_DecalSetName( tableIndex, (char *)name );
}


//-----------------------------------------------------------------------------
// Purpose: sets friends info locally to be sent to other users
//-----------------------------------------------------------------------------
void CClientState::SetFriendsID( uint friendsID, const char *friendsName )
{
	m_nFriendsID = friendsID;
	Q_strncpy( m_FriendsName, friendsName, sizeof(m_FriendsName) );
}


void CClientState::CheckOthersCustomFile( CRC32_t crcValue )
{
	if ( crcValue == 0 )
		return; // not a valid custom file

	if ( !cl_allowdownload.GetBool() )
		return; // client doesn't want to download anything

	CCustomFilename filehex( crcValue );

	if ( g_pFileSystem->FileExists( filehex.m_Filename, "game" ) )
		return; // we already have this file (assuming the CRC is correct)

	// we don't have it, request download from server
	m_NetChannel->RequestFile( filehex.m_Filename );
}

void CClientState::AddCustomFile( int slot, const char *resourceFile)
{
	if ( Q_strlen(resourceFile) <= 0 )
		return; // no resource file given

	if ( !COM_IsValidPath( resourceFile ) )
	{
		Msg("Customization file '%s' has invalid path.\n", resourceFile  );
		return;
	}

	if ( slot < 0 || slot >= MAX_CUSTOM_FILES )
		return; // wrong slot

	if ( !g_pFileSystem->FileExists( resourceFile ) )
	{
		DevMsg("Couldn't find customization file '%s'.\n", resourceFile );
		return; // resource file doesn't exits
	}

	if ( g_pFileSystem->Size( resourceFile ) > MAX_CUSTOM_FILE_SIZE )
	{
		Msg("Customization file '%s' is too big ( >%i bytes).\n", resourceFile, MAX_CUSTOM_FILE_SIZE );
		return; // resource file doesn't exits
	}

	CRC32_t crcValue;

	// Compute checksum of resource file
	CRC_File( &crcValue, resourceFile );

	// Copy it into materials/downloads if it's not there yet, so the server doesn't have to 
	// transmit the file back to us.
	bool bCopy = true;
	CCustomFilename filehex( crcValue );
	char szAbsFilename[ MAX_PATH ];
	if ( g_pFileSystem->RelativePathToFullPath( filehex.m_Filename, "game", szAbsFilename, sizeof(szAbsFilename), FILTER_CULLPACK ) )
	{
		// check if existing file already has same CRC, 
		// then we don't need to copy it anymore
		CRC32_t test;
		CRC_File( &test, szAbsFilename );
		if ( test == crcValue )
			bCopy = false;
	}

	if ( bCopy )
	{
		// Copy it over under the new name

		// Load up the file
		CUtlBuffer buf;
		if ( !g_pFileSystem->ReadFile( resourceFile, "game", buf ) )
		{
			Warning( "CacheCustomFiles: can't read '%s'.\n", resourceFile );
			return;
		}

		// Make sure dest directory exists
		char szParentDir[ MAX_PATH ];
		V_ExtractFilePath( filehex.m_Filename, szParentDir, sizeof(szParentDir) );
		g_pFileSystem->CreateDirHierarchy( szParentDir, "download" );

		// Save it
		if ( !g_pFileSystem->WriteFile( filehex.m_Filename, "download", buf ) )
		{
			Warning( "CacheCustomFiles: can't write '%s'.\n", filehex.m_Filename );
			return;
		}
	}

	/* Finally, validate the VTF file. TODO
	CUtlVector<char> fileData;
	if ( LogoFile_ReadFile( crcValue, fileData ) )
	{
		bValid = true;
	}
	else
	{
		Warning( "CL_LogoFile_OnConnect: logo file '%s' invalid.\n", logotexture );
	} */

	m_nCustomFiles[slot].crc = crcValue; // first slot is logo
	m_nCustomFiles[slot].reqID = 0;

}

void CClientState::CheckOwnCustomFiles()
{
	// clear file CRCs
	Q_memset( m_nCustomFiles, 0, sizeof(m_nCustomFiles) );
	
	if ( m_nMaxClients == 1 )
		return;	// not in singleplayer

	if ( IsPC() )
	{
		AddCustomFile( 0, cl_logofile.GetString() );
		AddCustomFile( 1, cl_soundfile.GetString() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CClientState::DumpPrecacheStats( const char * name )
{
	if ( !name || !name[0] )
	{
		ConMsg( "Can only dump stats when active in a level\n" );
		return;
	}

	CPrecacheItem *items = NULL;
	
	if ( !Q_strcmp(MODEL_PRECACHE_TABLENAME, name ) )
	{
		items = model_precache;
	}
	else if ( !Q_strcmp(GENERIC_PRECACHE_TABLENAME, name ) )
	{
		items = generic_precache;
	}
	else if ( !Q_strcmp(SOUND_PRECACHE_TABLENAME, name ) )
	{
		items = sound_precache;
	}
	else if ( !Q_strcmp(DECAL_PRECACHE_TABLENAME, name ) )
	{
		items = decal_precache;
	}

	INetworkStringTable *table = GetStringTable( name );

	if ( !items || !table)
	{
		ConMsg( "Precache table '%s' not found.\n", name );
		return;
	}

	int count =  table->GetNumStrings();
	int maxcount = table->GetMaxStrings();

	ConMsg( "\n" );
	ConMsg( "Precache table %s:  %i of %i slots used\n", table->GetTableName(),
		count, maxcount );

	for ( int i = 0; i < count; i++ )
	{
		char const *pchName = table->GetString( i );
		CPrecacheItem *slot = &items[ i ];
		const CPrecacheUserData *p = CL_GetPrecacheUserData( table, i );

		if ( !pchName || !slot || !p )
			continue;

		ConMsg( "%03i:  %s (%s):   ",
			i,
			pchName,
			GetFlagString( p->flags ) );


		if ( slot->GetReferenceCount() == 0 )
		{
			ConMsg( " never used\n" );
		}
		else
		{
			ConMsg( " %i refs, first %.2f mru %.2f\n",
				slot->GetReferenceCount(), 
				slot->GetFirstReference(), 
				slot->GetMostRecentReference() );
		}
	}

	ConMsg( "\n" );
}

void CClientState::ReadDeletions( CEntityReadInfo &u )
{
	VPROF( "ReadDeletions" );
	while ( u.m_pBuf->ReadOneBit()!=0 )
	{
		int idx = u.m_pBuf->ReadUBitLong( MAX_EDICT_BITS );	
		
		Assert( !u.m_pTo->transmit_entity.Get( idx ) );

		CL_DeleteDLLEntity( idx, "ReadDeletions" );
	}
}

void CClientState::ReadEnterPVS( CEntityReadInfo &u )
{
	VPROF( "ReadEnterPVS" );

	TRACE_PACKET(( "  CL Enter PVS (%d)\n", u.m_nNewEntity ));

	int iClass = u.m_pBuf->ReadUBitLong( m_nServerClassBits );

	int iSerialNum = u.m_pBuf->ReadUBitLong( NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS );

	CL_CopyNewEntity( u, iClass, iSerialNum );

	if ( u.m_nNewEntity == u.m_nOldEntity ) // that was a recreate
		u.NextOldEntity();
}

void CClientState::ReadLeavePVS( CEntityReadInfo &u )
{
	VPROF( "ReadLeavePVS" );
	// Sanity check.
	if ( !u.m_bAsDelta )
	{
		Assert(0); // cl.validsequence = 0;
		ConMsg( "WARNING: LeavePVS on full update" );
		u.m_UpdateType = Failed;	// break out
		return;
	}

	Assert( !u.m_pTo->transmit_entity.Get( u.m_nOldEntity ) );

	if ( u.m_UpdateFlags & FHDR_DELETE )
	{
		CL_DeleteDLLEntity( u.m_nOldEntity, "ReadLeavePVS" );
	}

	u.NextOldEntity();
}

void CClientState::ReadDeltaEnt( CEntityReadInfo &u )
{
	VPROF( "ReadDeltaEnt" );
	CL_CopyExistingEntity( u );
	
	u.NextOldEntity();
}

void CClientState::ReadPreserveEnt( CEntityReadInfo &u )
{
	VPROF( "ReadPreserveEnt" );
	if ( !u.m_bAsDelta )  // Should never happen on a full update.
	{
		Assert(0); // cl.validsequence = 0;
		ConMsg( "WARNING: PreserveEnt on full update" );
		u.m_UpdateType = Failed;	// break out
		return;
	}

	Assert( u.m_pFrom->transmit_entity.Get(u.m_nOldEntity) );

	// copy one of the old entities over to the new packet unchanged

	// XXX(JohnS): This was historically checking for NewEntity overflow, though this path does not care (and new entity
	//             may be -1).  The old entity bounds check here seems like what was intended, but since nNewEntity
	//             should not be overflowed either, I've left that check in case it was guarding against a case I am
	//             overlooking.
	if ( u.m_nOldEntity >= MAX_EDICTS || u.m_nOldEntity < 0 || u.m_nNewEntity >= MAX_EDICTS )
	{
		Host_Error( "CL_ReadPreserveEnt: Entity out of bounds. Old: %i, New: %i",
		            u.m_nOldEntity, u.m_nNewEntity );
	}

	u.m_pTo->last_entity = u.m_nOldEntity;
	u.m_pTo->transmit_entity.Set( u.m_nOldEntity );

	// Zero overhead
	if ( cl_entityreport.GetBool() )
		CL_RecordEntityBits( u.m_nOldEntity, 0 );

	CL_PreserveExistingEntity( u.m_nOldEntity );

	u.NextOldEntity();
}


//-----------------------------------------------------------------------------
// Purpose: Starts checking that all the necessary files are local
//-----------------------------------------------------------------------------
void CClientState::StartUpdatingSteamResources()
{
	if ( IsX360() )
	{
		return;
	}

	// we can only do this when in SIGNONSTATE_NEW, 
	// since the completion of this triggers the continuation of SIGNONSTATE_NEW
	Assert(m_nSignonState == SIGNONSTATE_NEW);

	// make sure we have all the necessary resources locally before continuing
	m_hWaitForResourcesHandle = g_pFileSystem->WaitForResources(m_szLevelBaseName);
	m_bUpdateSteamResources = false;
	m_bShownSteamResourceUpdateProgress = false;
	m_bDownloadResources = false;
	m_bPrepareClientDLL = true;
}

//-----------------------------------------------------------------------------
// Purpose: checks to see if we're done updating files
//-----------------------------------------------------------------------------
void CClientState::CheckUpdatingSteamResources()
{
	if ( IsX360() )
	{
		return;
	}

	VPROF_BUDGET( "CheckUpdatingSteamResources", VPROF_BUDGETGROUP_STEAM );

	if ( m_bPrepareClientDLL )
	{
		float flPrepareProgress = 0.f;
		char szMutableLevelName[ sizeof( m_szLevelBaseName ) ] = { 0 };
		V_strncpy( szMutableLevelName, m_szLevelBaseName, sizeof( szMutableLevelName ) );

		// if the game .dll doesn't support this call assume everything is prepared
		IServerGameDLL::ePrepareLevelResourcesResult eResult = IServerGameDLL::ePrepareLevelResources_Prepared;
		if ( g_iServerGameDLLVersion >= 10 )
		{
			eResult = serverGameDLL->AsyncPrepareLevelResources( szMutableLevelName, sizeof( szMutableLevelName ),
				m_szLevelFileName, sizeof( m_szLevelFileName ), &flPrepareProgress );
		}

		switch ( eResult )
		{
		case IServerGameDLL::ePrepareLevelResources_InProgress:
			if (!m_bShownSteamResourceUpdateProgress)
			{
				// make sure the loading dialog is up
				EngineVGui()->StartCustomProgress();
				EngineVGui()->ActivateGameUI();
				m_bShownSteamResourceUpdateProgress = true;
			}
			EngineVGui()->UpdateCustomProgressBar( flPrepareProgress, g_pVGuiLocalize->Find("#Valve_UpdatingSteamResources") );
			break;
		default:
		case IServerGameDLL::ePrepareLevelResources_Prepared:
			flPrepareProgress = 100.f;
			m_bPrepareClientDLL = false;
			m_bUpdateSteamResources = true;
			break;
		}
	}

	if (m_bUpdateSteamResources)
	{
		bool bComplete = false;
		float flProgress = 0.0f;
		g_pFileSystem->GetWaitForResourcesProgress(m_hWaitForResourcesHandle, &flProgress, &bComplete);

		if (bComplete)
		{
			m_hWaitForResourcesHandle = NULL;
			m_bUpdateSteamResources = false;
			m_bDownloadResources = false;

			if ( m_pDownloadableFileTable )
			{
				bool allowDownloads = true;
				bool allowSoundDownloads = true;
				bool allowNonMaps = true;
				if ( !Q_strcasecmp( cl_downloadfilter.GetString(), "none" ) )
				{
					allowDownloads = allowSoundDownloads = allowNonMaps = false;
				}
				else if ( !Q_strcasecmp( cl_downloadfilter.GetString(), "nosounds" ) )
				{
					allowSoundDownloads = false;
				}
				else if ( !Q_strcasecmp( cl_downloadfilter.GetString(), "mapsonly" ) )
				{
					allowNonMaps = false;
				}

				if ( allowDownloads )
				{
					char extension[4];
					for ( int i=0; i<m_pDownloadableFileTable->GetNumStrings(); ++i )
					{
						const char *fname = m_pDownloadableFileTable->GetString( i );

						if ( !allowSoundDownloads )
						{
							Q_ExtractFileExtension( fname, extension, sizeof( extension ) );
							if ( !Q_strcasecmp( extension, "wav" ) || !Q_strcasecmp( extension, "mp3" ) )
							{
								continue;
							}
						}

						if ( !allowNonMaps )
						{
							// The user wants maps only.
							Q_ExtractFileExtension( fname, extension, sizeof( extension ) );

							// If the extension is not bsp, skip it.
							if ( Q_strcasecmp( extension, "bsp" ) )
							{
								continue;
							}
						}

						CL_QueueDownload( fname );
					}
				}

				if ( CL_GetDownloadQueueSize() )
				{
					// make sure the loading dialog is up
					EngineVGui()->StartCustomProgress();
					EngineVGui()->ActivateGameUI();
					m_bDownloadResources = true;
				}
				else
				{
					m_bDownloadResources = false;
					FinishSignonState_New();
				}
			}
			else
			{
				Host_Error( "Invalid download file table." );
			}
		}
		else if (flProgress > 0.0f)
		{
			if (!m_bShownSteamResourceUpdateProgress)
			{
				// make sure the loading dialog is up
				EngineVGui()->StartCustomProgress();
				EngineVGui()->ActivateGameUI();
				m_bShownSteamResourceUpdateProgress = true;
			}

			// change it to be updating steam resources
			EngineVGui()->UpdateCustomProgressBar( flProgress, g_pVGuiLocalize->Find("#Valve_UpdatingSteamResources") );
		}
	}

	if ( m_bDownloadResources )
	{
		// Check on any HTTP downloads in progress
		bool stillDownloading = CL_DownloadUpdate();

		if ( !stillDownloading )
		{
			m_bDownloadResources = false;
			FinishSignonState_New();
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: At a certain rate, this function will verify any unverified
// file CRCs with the server.
//-----------------------------------------------------------------------------
void CClientState::CheckFileCRCsWithServer()
{
//! !FIXME! Stubbed this.  Several reasons:
//!
//! 1.) Removed the CRC functionality (because it was broken when we switched to use MD5's for hashes of
//!     loose files, but the server only has CRC's of some files in the VPK headers.).  Currently the only
//!     supported pure server mode is "trusted source."
//! 2.) Sending MD5's of VPK's is a bit too restrictive for most use cases.  For example, if a client
//!     has an extra VPK for custom content, the server doesn't know what to do with it.  Or if we
//!     release an optional update, the VPK's might legitimately differ.
//!
//! Rich has pointed out that we really need pure server client work to be something that the client
//! cannot easily bypass.  Currently that is the case.  But I need to ship the SteamPipe conversion now.
//! We can revisit pure server security after that has shipped.
//
//	VPROF_( "CheckFileCRCsWithServer", 1, VPROF_BUDGETGROUP_OTHER_NETWORKING, false, BUDGETFLAG_CLIENT );
//	const float flBatchInterval = 1.0f / 5.0f;
//	const int nBatchSize = 5;
//
//	// Don't do this yet..
//	if ( !m_bCheckCRCsWithServer )
//		return;
//
//	if ( m_nSignonState != SIGNONSTATE_FULL )
//		return;
//
//	// Only send a batch every so often.
//	float flCurTime = Plat_FloatTime();
//	if ( (flCurTime - m_flLastCRCBatchTime) < flBatchInterval )
//		return;
//
//	m_flLastCRCBatchTime = flCurTime;
//
//	CUnverifiedFileHash rgUnverifiedFiles[nBatchSize];
//	int count = g_pFileSystem->GetUnverifiedFileHashes( rgUnverifiedFiles, ARRAYSIZE( rgUnverifiedFiles ) );
//	if ( count == 0 )
//		return;
//
//	// Send the messages to the server.
//	for ( int i=0; i < count; i++ )
//	{
//		CLC_FileCRCCheck crcCheck;
//		V_strncpy( crcCheck.m_szPathID, rgUnverifiedFiles[i].m_PathID, sizeof( crcCheck.m_szPathID ) );
//		V_strncpy( crcCheck.m_szFilename, rgUnverifiedFiles[i].m_Filename, sizeof( crcCheck.m_szFilename ) );
//		crcCheck.m_nFileFraction = rgUnverifiedFiles[i].m_nFileFraction;
//		crcCheck.m_MD5 = rgUnverifiedFiles[i].m_FileHash.m_md5contents;
//		crcCheck.m_CRCIOs = rgUnverifiedFiles[i].m_FileHash.m_crcIOSequence;
//		crcCheck.m_eFileHashType = rgUnverifiedFiles[i].m_FileHash.m_eFileHashType;
//		crcCheck.m_cbFileLen = rgUnverifiedFiles[i].m_FileHash.m_cbFileLen;
//		crcCheck.m_nPackFileNumber = rgUnverifiedFiles[i].m_FileHash.m_nPackFileNumber;
//		crcCheck.m_PackFileID = rgUnverifiedFiles[i].m_FileHash.m_PackFileID;
//
//		m_NetChannel->SendNetMsg( crcCheck );
//	}
}


//-----------------------------------------------------------------------------
// Purpose: sanity-checks the variables in a VMT file to prevent the client from
// making player etc. textures that glow or show through walls etc.  Anything
// other than $baseTexture and $bumpmap is hereby verboten.
//-----------------------------------------------------------------------------
bool CheckSimpleMaterial( IMaterial *pMaterial )
{
	if ( !pMaterial )
		return false;

	const char *name = pMaterial->GetShaderName();
	if ( Q_strncasecmp( name, "VertexLitGeneric", 16 ) &&
		 Q_strncasecmp( name, "UnlitGeneric", 12 ) )
		return false;

	if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_IGNOREZ ) )
		return false;

	if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_WIREFRAME ) )
		return false;

	if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_SELFILLUM ) )
		return false;

	if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_ADDITIVE ) )
		return false;

	if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_NOFOG ) )
		return false;

	if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_HALFLAMBERT ) )
		return false;

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: find a filename in the string table, ignoring case and slash mismatches.  Returns the index, or INVALID_STRING_INDEX if not found.
//-----------------------------------------------------------------------------
int FindFilenameInStringTable( INetworkStringTable *table, const char *searchFname )
{
	char searchFilename[MAX_PATH];
	char tableFilename[MAX_PATH];

	Q_strncpy( searchFilename, searchFname, MAX_PATH );
	Q_FixSlashes( searchFilename );

	for ( int i=0; i<table->GetNumStrings(); ++i )
	{
		const char *tableFname = table->GetString( i );
		Q_strncpy( tableFilename, tableFname, MAX_PATH );
		Q_FixSlashes( tableFilename );

		if ( !Q_strcasecmp( searchFilename, tableFilename ) )
		{
			return i;
		}
	}

	return INVALID_STRING_INDEX;
}

//-----------------------------------------------------------------------------
// Purpose: find a filename in the string table, ignoring case and slash mismatches.
// Returns the consistency type, with CONSISTENCY_NONE being a Not Found result.
//-----------------------------------------------------------------------------
ConsistencyType GetFileConsistencyType( INetworkStringTable *table, const char *searchFname )
{
	int index = FindFilenameInStringTable( table, searchFname );
	if ( index == INVALID_STRING_INDEX )
	{
		return CONSISTENCY_NONE;
	}

	int length = 0;
	unsigned char *userData = NULL;
	userData = (unsigned char *)table->GetStringUserData( index, &length );
	if ( userData && length == sizeof( ExactFileUserData ) )
	{
		switch ( userData[0] )
		{
		case CONSISTENCY_EXACT:
		case CONSISTENCY_SIMPLE_MATERIAL:
			return (ConsistencyType)userData[0];
		default:
			return CONSISTENCY_NONE;
		}
	}
	else
	{
		return CONSISTENCY_NONE;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Does a CRC check compared to the CRC stored in the user data.
//-----------------------------------------------------------------------------
bool CheckCRCs( unsigned char *userData, int length, const char *filename )
{
	if ( userData && length == sizeof( ExactFileUserData ) )
	{
		if ( userData[0] != CONSISTENCY_EXACT && userData[0] != CONSISTENCY_SIMPLE_MATERIAL )
		{
			return false;
		}

		ExactFileUserData *exactFileData = (ExactFileUserData *)userData;

		CRC32_t crc;
		if ( !CRC_File( &crc, filename ) )
		{
			return false;
		}

		return ( crc == exactFileData->crc );
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: completes the SIGNONSTATE_NEW state
//-----------------------------------------------------------------------------
void CClientState::FinishSignonState_New()
{
	// make sure we're still in the right signon state
	if (m_nSignonState != SIGNONSTATE_NEW)
		return;

	if ( !m_bMarkedCRCsUnverified )
	{
		// Mark all file CRCs unverified once per server. We may have verified CRCs for certain files on
		// the previous server, but we need to reverify them on the new server.
		m_bMarkedCRCsUnverified = true;
		g_pFileSystem->MarkAllCRCsUnverified();
	}

	// Verify the map and player .mdl crc's now that we've finished downloading missing resources (maps etc)
	if ( !CL_CheckCRCs( m_szLevelFileName ) )
	{
		Host_Error( "Unable to verify map %s", ( m_szLevelFileName && m_szLevelFileName[0] ) ? m_szLevelFileName : "unknown" );
		return;
	}

	// We're going to force-touch a lot of textures and resources below, we don't want the streaming system to try and
	// pull these in as if they were being used for normal rendering.
	materials->SuspendTextureStreaming();

	// Only do this if our server is shut down and we're acting as a client. Otherwise the server handles this when it
	// starts the load.
	if ( sv.m_State < ss_loading )
	{
		// Reset the last used count on all models before beginning the new load -- The nServerCount value on models should
		// always resolve as different from values from previous servers.
		modelloader->ResetModelServerCounts();
	}

	if ( cl_always_flush_models.GetBool() )
	{
		modelloader->PurgeUnusedModels();
	}

	// Our load process is causing some very bad allocation & performance impact in drivers, due to the enormous amount
	// of resources being used (and, in the case of EnableHDR(), released and re-uploaded) in a single frame.  Ensuring
	// that we push a few frames through before and after the main model loading spree results in dramatically better
	// results.  Without these calls, for instance, OS X on high texture quality cannot survive map load.
	//
	// This is pretty janky, but doesn't really have any cost (and even makes our one-frozen-frame load screen slightly
	// less likely to trigger OS "not responding" warnings)
	extern void V_RenderSwapBuffers();
	V_RenderSwapBuffers();

	// Before we do anything with the whitelist, make sure we have the proper map pack mounted
	// this will load the .bsp by setting the world model the string list at the hardcoded index 1.
	cl.SetModel( 1 );

	V_RenderSwapBuffers();

	// Check for a new whitelist. It's good to do it early in the connection process here because if we wait until later,
	// the client may have loaded some files w/o the proper whitelist restrictions and we'd have to reload them.
	m_bCheckCRCsWithServer = false;	// Don't check CRCs yet.. wait until we got a whitelist and cleaned out our files based on it to send CRCs.

	// We check the new whitelist now so loads that happen between here and FullyConnected are checked against it, but
	// don't trigger a reload of dirty files until after we flush unused resources from the last map in
	// CL_FullyConnected. This fixes reloading materials that are unused, and trying to reload materials that are no
	// longer pure, but would be unloaded in fully connected anyway.
	CL_CheckForPureServerWhitelist( m_pPendingPureFileReloads );

	CL_InstallAndInvokeClientStringTableCallbacks();

	materials->CacheUsedMaterials();

	// force a consistency check
	ConsistencyCheck( true );

	CL_RegisterResources();

	// Done with all resources, issue prespawn command.
	// Include server count in case server disconnects and changes level during d/l

	// Tell rendering system we have a new set of models.
	R_LevelInit();

	// Balanced against SuspendTextureStreaming above
	materials->ResumeTextureStreaming();

	EngineVGui()->UpdateProgressBar(PROGRESS_SENDCLIENTINFO);
	if ( !m_NetChannel )
		return;

	SendClientInfo();

	CL_SetSteamCrashComment();

	// tell server that we entered now that state
	m_NetChannel->SendNetMsg( NET_SignonState( m_nSignonState, m_nServerCount ) );
}


//-----------------------------------------------------------------------------
// Purpose: run a file consistency check if enforced by server
//-----------------------------------------------------------------------------
void CClientState::ConsistencyCheck(bool bChanged )
{
	// get the default config for the current card as a starting point.
	// server must have sent us this table
	if ( !m_pDownloadableFileTable )
		return;

	// no checks during single player or demo playback
	if( (m_nMaxClients == 1) || demoplayer->IsPlayingBack() )
		return;

	// only if we are connected
	if ( !IsConnected() )
		return;

	// check if material configuration changed
	static MaterialSystem_Config_t s_LastConfig;
	MaterialSystem_Config_t newConfig = materials->GetCurrentConfigForVideoCard();

	if ( Q_memcmp( &s_LastConfig, &newConfig, sizeof(MaterialSystem_Config_t) ) )
	{
		// remember last config we tested
		s_LastConfig = newConfig;
		bChanged = true;
	}

	if ( !bChanged )
		return;

#if 0
	// This check was removed as GetSteamUniverse() is very expensive and was getting
	//	called every frame on Linux. I don't believe it should have been merged over from
	//	l4d2 in the first place either...
	// If we're not signed into the steam universe, then bail.
	//  This should only happen when we're developing internally without Steam I hope?
	if ( k_EUniverseInvalid == GetSteamUniverse() )
	{
		return;
	}
#endif

	const char *errorMsg = NULL;
	const char *errorFilename = NULL;

	// check CRCs and model sizes
	Color red(  200,  20,  20, 255 );
	Color blue( 100, 100, 200, 255 );
	for ( int i=0; i<m_pDownloadableFileTable->GetNumStrings(); ++i )
	{
		int length = 0;
		unsigned char *userData = NULL;
		userData = (unsigned char *)m_pDownloadableFileTable->GetStringUserData( i, &length );
		const char *filename = m_pDownloadableFileTable->GetString( i );

		//
		// CRC Check
		//
		if ( userData && userData[0] == CONSISTENCY_EXACT && length == sizeof( ExactFileUserData ) )
		{
			// Hm, this isn't supported anymore.  (Use sv_pure instead.)  Under ordinary
			// circumstances, servers shouldn't be asking for it.
			Assert( false );
			continue;
		}

		//
		// Bounds Check
		//
		// This is simply asking for the model's mins and maxs.  Also, it checks each material referenced
		// by the model, to make sure it doesn't ignore Z, isn't overbright, etc.
		//
		// TODO: Animations and facial expressions can still pull verts out past this.
		//
		else if ( userData && userData[0] == CONSISTENCY_BOUNDS && length == sizeof( ModelBoundsUserData ) )
		{
			ModelBoundsUserData *boundsData = (ModelBoundsUserData *)userData;
			model_t *pModel = modelloader->GetModelForName( filename, IModelLoader::FMODELLOADER_CLIENT );
			if ( !pModel )
			{
				errorMsg = "Cannot find required model";
				errorFilename = filename;
			}
			else
			{
				if ( pModel->mins.x < boundsData->mins.x ||
					pModel->mins.y < boundsData->mins.y ||
					pModel->mins.z < boundsData->mins.z )
				{
					ConColorMsg( red, "Model %s exceeds mins (%.1f %.1f %.1f vs. %.1f %.1f %.1f)\n", filename,
						pModel->mins.x, pModel->mins.y, pModel->mins.z,
						boundsData->mins.x, boundsData->mins.y, boundsData->mins.z);
					errorMsg = "Server is enforcing model bounds";
					errorFilename = filename;
				}
				if ( pModel->maxs.x > boundsData->maxs.x ||
					pModel->maxs.y > boundsData->maxs.y ||
					pModel->maxs.z > boundsData->maxs.z )
				{
					ConColorMsg( red, "Model %s exceeds maxs (%.1f %.1f %.1f vs. %.1f %.1f %.1f)\n", filename,
						pModel->maxs.x, pModel->maxs.y, pModel->maxs.z,
						boundsData->maxs.x, boundsData->maxs.y, boundsData->maxs.z);
					errorMsg = "Server is enforcing model bounds";
					errorFilename = filename;
				}

				// Check each texture
				IMaterial *pMaterials[ 128 ];
				int materialCount = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials );

				for ( int j = 0; j<materialCount; ++j )
				{
					IMaterial *pMaterial = pMaterials[j];

					if ( !CheckSimpleMaterial( pMaterial ) )
					{
						ConColorMsg( red, "Model %s has a bad texture %s\n", filename, pMaterial->GetName() );
						errorMsg = "Server is enforcing simple material";
						errorFilename = pMaterial->GetName();
						break;
					}
				}
			}
		}
	}

	if ( errorFilename && *errorFilename )
	{
		COM_ExplainDisconnection( true, "%s:\n%s\n", errorMsg, errorFilename );
		Host_Error( "%s: %s\n", errorMsg, errorFilename );
	}
}

void CClientState::UpdateAreaBits_BackwardsCompatible()
{
	if ( m_pAreaBits )
	{
		tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

		memcpy( m_chAreaBits, m_pAreaBits, sizeof( m_chAreaBits ) );
		
		// The whole point of adding this array was that the client could react to closed portals.
		// If they're using the old interface to set area portal bits, then we use the old 
		// behavior of assuming all portals are open on the clent.
		memset( m_chAreaPortalBits, 0xFF, sizeof( m_chAreaPortalBits ) );

		m_bAreaBitsValid = true;
	}
}


unsigned char** CClientState::GetAreaBits_BackwardCompatibility()
{
	return &m_pAreaBits;
}


void CClientState::RunFrame()
{
	CBaseClientState::RunFrame();

	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

	// Since cl_rate is a virtualized cvar, make sure to pickup changes in it.
	if ( m_NetChannel )
		m_NetChannel->SetDataRate( cl_rate->GetFloat() );

	ConsistencyCheck( false );

	// Check if paged pool is low ( < 8% free )
	static bool s_bLowPagedPoolMemoryWarning = false;
	PAGED_POOL_INFO_t ppi;
	if ( ( SYSCALL_SUCCESS == Plat_GetPagedPoolInfo( &ppi ) ) &&
		( ( ppi.numPagesFree * 12 ) < ( ppi.numPagesUsed + ppi.numPagesFree ) ) )
	{
		con_nprint_t np;
		np.time_to_live = 1.0;
		np.index = 1;
		np.fixed_width_font = false;
		np.color[ 0 ] = 1.0;
		np.color[ 1 ] = 0.2;
		np.color[ 2 ] = 0.0;
		Con_NXPrintf( &np, "WARNING:  OS Paged Pool Memory Low" );

		// Also print a warning to console
		static float s_flLastWarningTime = 0.0f;
		if ( !s_bLowPagedPoolMemoryWarning ||
			 ( Plat_FloatTime() - s_flLastWarningTime > 3.0f ) )	// print a warning no faster than once every 3 sec
		{
			s_bLowPagedPoolMemoryWarning = true;
			s_flLastWarningTime = Plat_FloatTime();
			Warning( "OS Paged Pool Memory Low!\n" );
			Warning( "  Currently using %lu pages (%lu Kb) of total %lu pages (%lu Kb total)\n",
				ppi.numPagesUsed, ppi.numPagesUsed * Plat_GetMemPageSize(),
				( ppi.numPagesFree + ppi.numPagesUsed ), ( ppi.numPagesFree + ppi.numPagesUsed ) * Plat_GetMemPageSize() );
			Warning( "  Please see http://www.steampowered.com for more information.\n" );
		}
	}
	else if ( s_bLowPagedPoolMemoryWarning )
	{
		s_bLowPagedPoolMemoryWarning = false;
		Msg( "Info: OS Paged Pool Memory restored - currently %lu pages free (%lu Kb) of total %lu pages (%lu Kb total).\n",
			ppi.numPagesFree, ppi.numPagesFree * Plat_GetMemPageSize(),
			( ppi.numPagesFree + ppi.numPagesUsed ), ( ppi.numPagesFree + ppi.numPagesUsed ) * Plat_GetMemPageSize() );
	}

}
